package com.ruoyi.swap.openapi.Kline.controller;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.enums.KlineTypeEnum;
import com.ruoyi.swap.constants.SwapMonitorConstants;
import com.ruoyi.swap.openapi.Kline.domain.SwapOpenapiKline;
import com.ruoyi.swap.openapi.Kline.domain.VO.SwapOpenapiKlineVO;
import com.ruoyi.util.LarkDangerRobotUtils;
import com.ruoyi.util.LarkRecoverRobotUtils;
import com.ruoyi.util.MergeUtil;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.swap.openapi.Kline.service.ISwapOpenapiKlineService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;

/**
 * K线/蜡烛图Controller
 * 
 * @author saumell
 * @date 2024-08-19
 */
@RestController
@RequestMapping("/sw_api_Kline/sw_api_Kline_bak")
public class SwapOpenapiKlineController extends BaseController
{
    private static final Logger log = LoggerFactory.getLogger(SwapOpenapiKlineController.class);

    @Value("${my.lark.dangerlark}")
    private String dangerlark;

    @Value("${my.lark.recoverlark}")
    private String recoverlark;

    @Autowired
    private ISwapOpenapiKlineService swapOpenapiKlineService;

    private static final Map<String, String> INTERVAL_CORRECTION_MAP;
    static {
        Map<String, String> map = new HashMap<>();
        map.put("1min", "1m");
        map.put("5min", "5m");
        map.put("15min", "15m");
        map.put("30min", "30m");
        map.put("60min", "1h");
        map.put("1day", "1d");
        map.put("1week", "1w");
        map.put("1month", "1M");
        INTERVAL_CORRECTION_MAP = java.util.Collections.unmodifiableMap(map);
    }

    /**
     * 查询K线/蜡烛图列表
     */
    @PreAuthorize("@ss.hasPermi('sw_api_Kline:sw_api_Kline_bak:list')")
    @GetMapping("/list")
    public TableDataInfo list(SwapOpenapiKlineVO swapOpenapiKline)
    {
        startPage();
        List<SwapOpenapiKlineVO> list = swapOpenapiKlineService.selectSwapOpenapiKlineList(swapOpenapiKline);
        return getDataTable(list);
    }

    /**
     * 导出K线/蜡烛图列表
     */
    @PreAuthorize("@ss.hasPermi('sw_api_Kline:sw_api_Kline_bak:export')")
    @Log(title = "K线/蜡烛图", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SwapOpenapiKlineVO swapOpenapiKline)
    {
        List<SwapOpenapiKlineVO> list = swapOpenapiKlineService.selectSwapOpenapiKlineList(swapOpenapiKline);
        ExcelUtil<SwapOpenapiKlineVO> util = new ExcelUtil<SwapOpenapiKlineVO>(SwapOpenapiKlineVO.class);
        util.exportExcel(response, list, "K线_蜡烛图数据");
    }

    private static String checkoutMessage(String openapiMessage, String timstampname, Long timestamp) {
        try {
            String trimmedResponse = openapiMessage.trim();

            if (trimmedResponse.startsWith("{")) {
                try {
                    com.alibaba.fastjson2.JSONObject errorObject = JSON.parseObject(trimmedResponse);
                    String apiErrorMsg = errorObject.getString("msg");
                    return "false, API返回错误信息: " + (apiErrorMsg != null ? apiErrorMsg : trimmedResponse);
                } catch (Exception parseException) {
                    return "false, API返回了无法解析的错误对象: " + trimmedResponse;
                }
            }

            JSONArray jsonArray = JSON.parseArray(trimmedResponse);
            if (jsonArray == null || jsonArray.isEmpty()) {
                return "true";
            }

            StringBuilder errors = new StringBuilder();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            boolean timestampOrderErrorLogged = false;
            boolean timestampGapErrorLogged = false;
            boolean priceGapErrorLogged = false;

            long previousIdx = -1;
            BigDecimal previousClose = null;

            for (int i = 0; i < jsonArray.size(); i++) {
                Object element = jsonArray.get(i);
                if (!(element instanceof JSONArray) || ((JSONArray) element).size() < 6) {
                    errors.append(String.format("第 %d 条K线结构不正确; ", i + 1));
                    continue;
                }
                JSONArray currentKline = (JSONArray) element;

                long currentIdx = currentKline.getLong(0);
                BigDecimal currentOpen = currentKline.getBigDecimal(1);
                BigDecimal currentHigh = currentKline.getBigDecimal(2);
                BigDecimal currentLow = currentKline.getBigDecimal(3);
                BigDecimal currentClose = currentKline.getBigDecimal(4);
                String readableTime = sdf.format(new Date(currentIdx));

                if (currentHigh.compareTo(currentOpen) < 0 || currentHigh.compareTo(currentClose) < 0 ||
                        currentLow.compareTo(currentOpen) > 0 || currentLow.compareTo(currentClose) > 0) {
                    errors.append(String.format("价格逻辑错误 at %s (O:%s H:%s L:%s C:%s); ", readableTime, currentOpen, currentHigh, currentLow, currentClose));
                }

                if (i > 0) {
                    if (!timestampOrderErrorLogged && currentIdx <= previousIdx) {
                        errors.append(String.format("时间戳排序错误 at %s; ", readableTime));
                        timestampOrderErrorLogged = true;
                    }

                    if (!timestampGapErrorLogged && (currentIdx - previousIdx) != timestamp) {
                        errors.append(String.format("时间跳空 at %s (间隔非%s); ", readableTime, timstampname));
                        timestampGapErrorLogged = true;
                    }

                    if (!priceGapErrorLogged && previousClose != null && previousClose.compareTo(currentOpen) != 0) {
                        errors.append(String.format("价格跳空 at %s (前收盘:%s, 现开盘:%s); ", readableTime, previousClose, currentOpen));
                        priceGapErrorLogged = true;
                    }
                }
                previousIdx = currentIdx;
                previousClose = currentClose;
            }

            if (errors.length() > 0) {
                return "false, " + errors.toString();
            }

            return "true";
        } catch (Exception e) {
            log.error("checkoutMessage 发生致命异常", e);
            return "false, K线数据校验时发生致命异常: " + e.getMessage();
        }
    }

    /**
     * 通过OpenAPI调用合约Kline
     */

    public void SwapOpenapiKline() {
        List<SwapOpenapiKlineVO> swapOpenAPIList = swapOpenapiKlineService.selectSwapAllData();
        List<Integer> dangerProductionInspections = new ArrayList<Integer>();
        List<Integer> recoverProductionInspections = new ArrayList<Integer>();
        for (SwapOpenapiKlineVO vo : swapOpenAPIList) {
            Integer id = vo.getId();
            String currencyPair = vo.getCurrencyPair();
            String callingState = vo.getCallingState();
            Date lastExecutionTime = vo.getLastExecutionTime();
            Integer successTimes = vo.getSuccessTimes();
            Integer failTimes = vo.getFailTimes();
            Integer dangerWarning = vo.getDangerWarning();
            Integer safeWarning = vo.getSafeWarning();
            String openapirequest = null;
            OkHttpClient client = new OkHttpClient().newBuilder().build();
            List<String> chekoutList = new ArrayList<>();
            try {
                for (KlineTypeEnum value : KlineTypeEnum.values()) {
                    String incorrectInterval = value.getType();
                    String correctInterval = INTERVAL_CORRECTION_MAP.getOrDefault(incorrectInterval, incorrectInterval);

                    Request request = new Request.Builder()
                            .url(MessageFormat.format(SwapMonitorConstants.SWAP_OPENAPI_KLINE, currencyPair, correctInterval, 100))
                            .get()
                            .build();
                    Response response = client.newCall(request).execute();
                    String responseBody = response.body().string();
                    openapirequest = checkoutMessage(responseBody, value.getType(), value.getTimestamp());
                    chekoutList.add(openapirequest);
                }
            } catch (Exception e) {
                // 记录请求阶段的IO等异常
                log.error("API请求执行异常 for " + currencyPair, e);
                // 确保即使请求失败，也有一个明确的失败结果进入列表
                chekoutList.add("false, API请求阶段异常: " + e.getMessage());
            }

            String reponseresult = MergeUtil.mergerespont(chekoutList);

            if (reponseresult == null || reponseresult.isEmpty() || !reponseresult.startsWith("true") && !reponseresult.startsWith("false")) {
                log.error("MergeUtil 返回了未知结果: " + reponseresult);
                reponseresult = "false, 内部结果合并异常";
            }

            if (reponseresult.startsWith("false")) {
                String reason = reponseresult.substring(7);
                if (id == null) {
                    // 首次失败 - 插入新记录
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    successTimes = 0;
                    failTimes = 1;
                    Integer currencysId = swapOpenapiKlineService.queryCurryNumber(currencyPair);
                    SwapOpenapiKline o1 = new SwapOpenapiKline(null, currencysId, "off_line", lastExecutionTime, successTimes, failTimes, reason, null, null, null);
                    swapOpenapiKlineService.insertSwapOpenapiKline(o1);
                    dangerProductionInspections.add(o1.getId());
                    log.info("[合约-OpenaAPI-Kline]：【{}】首次调用即失败, 原因: {}", currencyPair, reason);
                } else if ("on_line".equals(callingState)) {
                    // 从成功转为失败 - 原子更新
                    dangerProductionInspections.add(id);
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    swapOpenapiKlineService.incrementFailById(id, lastExecutionTime, "off_line", reason);
                    log.info("[合约-OpenaAPI-Kline]：【{}】从在线转为失败, 原因: {}", currencyPair, reason);
                } else { // "off_line" 或 null
                    // 持续失败 - 原子更新
                    if (dangerWarning == null || dangerWarning == 1) {
                        dangerProductionInspections.add(id);
                    }
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    swapOpenapiKlineService.incrementFailById(id, lastExecutionTime, "off_line", reason);
                    log.info("[合约-OpenaAPI-Kline]：【{}】持续失败, 原因: {}", currencyPair, reason);
                }
            } else { // 结果为 "true"
                if ("off_line".equals(callingState)) {
                    // 从失败恢复 - 原子更新
                    if (safeWarning == null || safeWarning == 1) {
                        recoverProductionInspections.add(id);
                    }
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    swapOpenapiKlineService.incrementSuccessById(id, lastExecutionTime, "on_line");
                    log.info("[合约-OpenaAPI-Kline]：【{}】从失败中恢复成功", currencyPair);
                } else { // "on_line" 或首次成功
                    if (id == null) {
                        // 首次成功 - 插入新记录
                        successTimes = 1;
                        failTimes = 0;
                        Integer currencysId = swapOpenapiKlineService.queryCurryNumber(currencyPair);
                        SwapOpenapiKline o1 = new SwapOpenapiKline(null, currencysId, "on_line", Timestamp.valueOf(LocalDateTime.now()), successTimes, failTimes, null, null, null, null);
                        swapOpenapiKlineService.insertSwapOpenapiKline(o1);
                        log.info("[合约-OpenaAPI-Kline]：【{}】首次巡检成功", currencyPair);
                    } else {
                        // 持续成功 - 原子更新
                        LocalDateTime currentDateTime = LocalDateTime.now();
                        lastExecutionTime = Timestamp.valueOf(currentDateTime);
                        swapOpenapiKlineService.incrementSuccessById(id, lastExecutionTime, "on_line");
                        log.info("[合约-OpenaAPI-Kline]：【{}】持续巡检成功", currencyPair);
                    }
                }
            }
        }

        List<SwapOpenapiKlineVO> swapKlineCurrencysList_new = swapOpenapiKlineService.selectSwapAllData();
        List<SwapOpenapiKlineVO> allProductionInspections2 = new ArrayList<>();
        List<SwapOpenapiKlineVO> allProductionInspections3 = new ArrayList<>();
        for (SwapOpenapiKlineVO item : swapKlineCurrencysList_new) {
            Integer id0 = item.getId();
            if (dangerProductionInspections.contains(id0)) {
                allProductionInspections2.add(item);
            } else if (recoverProductionInspections.contains(id0)) {
                allProductionInspections3.add(item);
            }
        }

        if (!allProductionInspections2.isEmpty()) {
            StringBuilder noticeBuilder = new StringBuilder("【合约-Kline-OpenaAPI】下方为行情K线/蜡烛图数据故障的交易对名称及调用时间，请您关注！\n");
            for (SwapOpenapiKlineVO item : allProductionInspections2) {
                String formattedDate = item.getLastExecutionTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item.getLastExecutionTime()) : "N/A";
                noticeBuilder.append(String.format("交易对名称: %s, 故障时间: %s, 故障原因: %s\n", item.getCurrencyPair(), formattedDate, item.getLastFailureReason()));
            }
            String dangernotice = LarkDangerRobotUtils.notice(noticeBuilder.toString(), dangerlark);
            boolean success = "lark_success".equals(dangernotice);
            for (SwapOpenapiKlineVO item : allProductionInspections2) {
                String statusMessage = success ? "Lark发送故障信息成功！" : "Lark发送故障信息失败！";
                SwapOpenapiKline o2 = new SwapOpenapiKline(item.getId(), null, null, null, null, null, null, success ? 0 : 1, null, statusMessage);
                swapOpenapiKlineService.updateSwapOpenapiKline(o2);
            }
        }

        if (!allProductionInspections3.isEmpty()) {
            StringBuilder noticeBuilder = new StringBuilder("【合约-Kline-OpenaAPI】下方为行情K线/蜡烛图数据恢复的交易对名称及调用时间，请您关注！\n");
            for (SwapOpenapiKlineVO item : allProductionInspections3) {
                String formattedDate = item.getLastExecutionTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(item.getLastExecutionTime()) : "N/A";
                noticeBuilder.append(String.format("交易对名称: %s, 恢复时间: %s\n", item.getCurrencyPair(), formattedDate));
            }
            String recovernotice = LarkRecoverRobotUtils.notice(noticeBuilder.toString(), recoverlark);
            boolean success = !"lark_fail".equals(recovernotice);
            for (SwapOpenapiKlineVO item : allProductionInspections3) {
                String statusMessage = success ? "Lark发送恢复信息成功！" : "Lark发送恢复信息失败！";
                SwapOpenapiKline o2 = new SwapOpenapiKline(item.getId(), null, null, null, null, null, null, null, success ? 0 : 1, statusMessage);
                swapOpenapiKlineService.updateSwapOpenapiKline(o2);
            }
        }
        log.info("============【【合约-OpenaAPI-Kline】执行完成============");


    } 
}
